/*
  (C) 2005 Adam D. Bradley

  This program is free software; you can redistribute it and/or
  modify it under the terms of Version 2 of the GNU General Public 
  License.
  
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.
  
  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free
  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

package snobject;

import java.io.*;
import java.util.Calendar;
import java.util.Date;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import step.Node;
import step.parse.SyntaxError;

public abstract class snObject
{
	protected Object content;
	protected long creationtime = -1;
	
	public snObject(){
		creationtime = Calendar.getInstance().getTimeInMillis();
	}
	
	public long getCreationTimeInMillis(){
		return creationtime;
	}
	
	public boolean equals(snObject other) {
		return content.equals(other.content);
	}

	final public Object getGenericContent() { return content; }
    
	public String toString(){
		return String.valueOf(content) ; 
	}
    
	public abstract String getXmlMimeType();
	public abstract String getWebMimeType();
	public abstract String getBinaryMimeType();
    
	// REQUIRES: Element e must be a snobject
	public static snObject xmlParse(org.w3c.dom.Element e)
	throws step.parse.SyntaxError, MimeException {
		String type = e.getAttribute("type");
		if(type.equalsIgnoreCase(MimeTypes.BOOLEAN))
   			return snBoolean.xmlParse(e);
		else if(type.equalsIgnoreCase(MimeTypes.STRING))
			return snString.xmlParse(e);
		else if(type.equalsIgnoreCase(MimeTypes.INTEGER))
			return snInteger.xmlParse(e);
		else if (type.equalsIgnoreCase(MimeTypes.DOUBLE))
			return snDouble.xmlParse(e);
		else if(type.equalsIgnoreCase(MimeTypes.COMMAND))
			return snCommand.xmlParse(e);
		else if (type.equalsIgnoreCase(MimeTypes.IMAGE))
			return snImage.xmlParse(e);
		else if (type.equalsIgnoreCase(MimeTypes.PAIR))
			return snPair.xmlParse(e);
		else if (type.equalsIgnoreCase(MimeTypes.STRUCT))
			return snStruct.xmlParse(e);
		else if (type.equalsIgnoreCase(MimeTypes.NIL))
			return new snNil();
		throw new MimeException(
			"Unknown Type: [" + type + 
			"] specified for object [" + e.toString()+ "]");
	}

	public static snObject binaryParse(java.io.File f) { /* DOME */
		/*
		 * snObject ret;
		 * if ((ret = snImage.binaryParse(f)) != null) 
		 *   return ret;
		 * return snString.binaryParse(f);
		 */
		return snNil.NIL;
	}

	public static snObject ParseSnObject(byte[] body)
		throws SyntaxError, MimeException
	{
		//System.out.println("Ok "+type);
		org.w3c.dom.Document d;
		ByteArrayInputStream bais = new ByteArrayInputStream(body);
		try {
			d = snbenchlib.XML.getDocumentBuilder().parse(bais);
		} catch (java.io.IOException ex) {
			throw new step.parse.SyntaxError("Unknown problem inside parse engine: " + ex.getMessage());
		} catch (org.xml.sax.SAXException ex) {
			throw new step.parse.SyntaxError(ex.getMessage());
		}
		//System.out.println("Ok until d: "+d.getDocumentElement().getNodeName());
		if (d.getDocumentElement().getNodeName().equals("snobject")){  
			return xmlParse(d.getDocumentElement());
		}else if (d.getDocumentElement().getNodeName().equals("stepgraph")){
			return new snStepProgram(new String(body));
		}else{
			throw new step.parse.SyntaxError("Do not know how to handle element "+d.getDocumentElement().getNodeName());
		}
	}
	
	// SJJ - SMOD: 
	//      This implementation was very limited, not being able to handle
	//      a stepprogram, which is a subtype of snobject itself.
	//      Improvised a quick solution
	public static snObject Parse(String type, byte [] body)
	throws SyntaxError, MimeException {
		if (MimeTypes.match(type)) {
			return ParseSnObject(body);
		} else {
			throw new MimeException("Unknown MIME type: "+type);
		}
	}
	
	/**
	 * Write <snobject type=[getXMLMimeType()]> to out with
	 * depth many indents before string.  Note no \n at end
	 * of this line
	 * 
	 * @param out
	 * @param depth
	 * @throws java.io.IOException
	 */
	protected void xmlWritePre(java.io.OutputStream out, int depth) throws java.io.IOException {
		Node.Indent(out, depth);
		if(creationtime!=-1)
			out.write(("<snobject " +
				"type=\"" + getXmlMimeType() + 
				//"\"" +" created=\"" + creationtime + 
				"\">").getBytes());
		else
			out.write(("<snobject " +
				"type=\"" + getXmlMimeType() + 
				"\">").getBytes());

	}
	/**
	 * Implementations of xmlWrite() should call xmlWritePre(), then write 
	 * their stuff, then call xmlWritePost(), then return.
	 */
	public abstract void xmlWrite(java.io.OutputStream out, int depth) throws java.io.IOException;

	
	/**
	 * Write </snobject>\n to out with
	 * depth many indents before string. 
	 * 
	 * @param out
	 * @param depth
	 * @throws java.io.IOException
	 */
	protected void xmlWritePost(java.io.OutputStream out, int depth) throws java.io.IOException {
		Node.Indent(out, depth);
		out.write("</snobject>\n".getBytes());
	}

	/**
	 * This is for writing web-browser-friendly stuff
	 */
	public abstract void webWrite(java.io.OutputStream out, int depth) throws java.io.IOException;

	/**
	 * This is for writing a flat bytestream instead of an XML encoding.
	 */
	public abstract void binaryWrite(java.io.OutputStream out) throws java.io.IOException;

	public final byte[] getXml() {
		java.io.ByteArrayOutputStream baos =
			new java.io.ByteArrayOutputStream();
		try {
			xmlWrite(baos,0);
		} catch (java.io.IOException ex) {
			return "Internal Error".getBytes();
		}
		return baos.toByteArray();
	}

	public final byte[] getWeb() {
		java.io.ByteArrayOutputStream baos =
			new java.io.ByteArrayOutputStream();
		try {
			webWrite(baos,0);
		} catch (java.io.IOException ex) {
			return "Internal Error".getBytes();
		}
		return baos.toByteArray();
	}

	public final byte[] getBinary() {
		java.io.ByteArrayOutputStream baos =
			new java.io.ByteArrayOutputStream();
		try {
			binaryWrite(baos);
		} catch (java.io.IOException ex) {
			return "Internal Error".getBytes();
		}
		return baos.toByteArray();
	}

	public String getAcceptableMimeType(java.util.Properties accept) {
		if (http.Server.Acceptable(accept, getXmlMimeType()) > 0.1) {
			return getXmlMimeType();
		} else if (http.Server.Acceptable(accept, getWebMimeType()) > 0.1) {
			return getWebMimeType();
		} else {
			return getBinaryMimeType();
		}
	}

	public String getAcceptable(java.util.Properties accept) {
		java.io.ByteArrayOutputStream baos =
			new java.io.ByteArrayOutputStream();
		try {
			if (http.Server.Acceptable(accept, getXmlMimeType()) > 0.1) {
				xmlWrite(baos,0);
			} else if (http.Server.Acceptable(accept, getWebMimeType()) > 0.1) {
				webWrite(baos,0);
			} else {
				binaryWrite(baos);
			}
		} catch (java.io.IOException ex) {
			return "Internal failure generating content...";
		}
		return baos.toString();
	}
	
	protected static Element getChildSNObject(org.w3c.dom.Node node){
		NodeList nl = node.getChildNodes();
		for(int i = 0; i < nl.getLength(); i++){
			org.w3c.dom.Node n = nl.item(i);
			if (n.getNodeName().equalsIgnoreCase("snobject"))
				return (Element)n;
		}
		return null;
	}
}
